#include <iostream>
#include <sstream>

/**
 * Include Lua Libraries
**/
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
    
/**
 * Include auxiliary libraries
**/
#include "lua_native_timer.h"
    
}

/**
 * Include the assocaited header
**/
#include <interface_lua.h>


extern "C" int lua_callback(struct lua_State *L);

LuaInterface::LUA_NIL LuaInterface::NIL;
std::vector<LuaInterface::Function> LuaInterface::function_registry;

const luaL_Reg SG2_functions[] = {
    {"callback_function", lua_callback},
    {NULL, NULL}
};

/**
 * Initialise lua
**/
LuaInterface::LuaInterface()
{
    /**
     * Initialise Lua
    **/
    m_state = luaL_newstate( );

    /**
     * Initialise Lua libraries
    **/
    luaL_openlibs( m_state );

    /**
     * Register the lua function callback
    **/
    luaL_register(m_state, "SG2",
        SG2_functions
    );
    
    /** Register the aux libs
     *
    **/
    loadAuxLibs();
}

LuaInterface::~LuaInterface()
{
    /**
     * Close Lua
    **/
    lua_close( m_state );
}

/**
 * Load and execute the script in the following file
 * NOTE: working directory may not be that which it's in
**/
bool LuaInterface::loadScript(std::string filename) const
{
    int status = 0;
    /**
     * Load the script
    **/
    status = luaL_loadfile( m_state, filename.c_str() );

    /**
     * Try to call the script
    **/
    if ( status == 0 ) status = lua_pcall( m_state, 0, LUA_MULTRET, 0 );

    /**
     * Report any errors
    **/
    if ( status != 0 )
    {
        std::cerr << "Lua error: " << lua_tostring( m_state, -1 ) << std::endl;
        lua_pop( m_state, 1 );
        return false;
    }

    return true;
}

void LuaInterface::error(std::string str) const {
    lua_pushstring( m_state, str.c_str() );
    lua_error( m_state );
}

/**
ISTREAM
**/
LuaInterface::istream::istream( struct lua_State *  state     )
    : m_state( state             ), m_argument(1) {}
LuaInterface::istream::istream( const LuaInterface& interface )
    : m_state( interface.m_state ), m_argument(1) {}
LuaInterface::istream& LuaInterface::istream::operator >> (LUA_NIL    value)
{
    lua_pushnil( m_state );
    ++m_argument;
    return *this;
}
LuaInterface::istream& LuaInterface::istream::operator >> (bool       value)
{
    lua_pushboolean( m_state, value );
    ++m_argument;
    return *this;
}
LuaInterface::istream& LuaInterface::istream::operator >> (void*      value)
{
    lua_pushlightuserdata( m_state, value );
    ++m_argument;
    return *this;
}
LuaInterface::istream& LuaInterface::istream::operator >> (float      value)
{
    lua_pushnumber( m_state, value );
    ++m_argument;
    return *this;
}
LuaInterface::istream& LuaInterface::istream::operator >> (int        value)
{
    lua_pushinteger( m_state, value );
    ++m_argument;
    return *this;
}
LuaInterface::istream& LuaInterface::istream::operator >> (std::string& value)
{
    lua_pushstring( m_state, value.c_str() );
    ++m_argument;
    return *this;
}
LuaInterface::istream& LuaInterface::istream::operator >> (LuaInterface& value)
{
    lua_pushthread( value.m_state );
    ++m_argument;
    return *this;
}
const size_t LuaInterface::istream::size() const
{
    return m_argument-1;
}

/**
OSTREAM
**/
LuaInterface::ostream::ostream( struct lua_State *  state     )
    : m_state( state             ), m_arguments( lua_gettop( m_state ) ), m_argument(1) {}
LuaInterface::ostream::ostream( const LuaInterface& interface )
    : m_state( interface.m_state ), m_arguments( lua_gettop( m_state ) ), m_argument(1) {}

LuaInterface::ostream& LuaInterface::ostream::operator << (LUA_NIL       value)
{
    lua_pop( m_state, 1 );
    return *this;
}
LuaInterface::ostream& LuaInterface::ostream::operator << (void*      & value)
{
    value = lua_touserdata( m_state, m_argument );
    ++m_argument;
    return *this;
}
LuaInterface::ostream& LuaInterface::ostream::operator << (float      & value)
{
    value = luaL_checknumber( m_state, m_argument );
    ++m_argument;
    return *this;
}
LuaInterface::ostream& LuaInterface::ostream::operator << (int        & value)
{
    value = luaL_checkint( m_state, m_argument );
    ++m_argument;
    return *this;
}
LuaInterface::ostream& LuaInterface::ostream::operator << (std::string& value)
{
    value = luaL_checkstring( m_state, m_argument );
    ++m_argument;
    return *this;
}
LuaInterface::ostream& LuaInterface::ostream::operator << (LuaInterface& value)
{
    #warning work on this
//    LUA_DEBUG(value = LuaInterface( lua_tothread( m_state, m_argument ) ));
    ++m_argument;
    return *this;
}
const size_t LuaInterface::ostream::size() const
{
    return m_arguments;
}

/**
 * LUA FUNCTION
**/
LuaInterface::ostream& operator << (LuaInterface::ostream& os, LuaInterface::Function& value) {
    int func;
    os << func;

    value = LuaInterface::function_registry[func];
    return os;
}

int lua_callback(struct lua_State *L) {
    LuaInterface::ostream parameters(L);
    LuaInterface::istream returns(L);

    if (parameters.size()<1) {
        luaL_error(L, "Wrong number of parameters sent (%i) requires at least one!", parameters.size());
        return 0;
    }

    LuaInterface::Function funcPtr;
    parameters << funcPtr;

    (*funcPtr)(returns, parameters);

    return returns.size();
}

void LuaInterface::registerFunction(std::string name, LuaInterface::Function func) {
    lua_pushinteger(m_state, function_registry.size());
    lua_setglobal(m_state, name.c_str());
    function_registry.push_back(func);
}

void LuaInterface::loadAuxLibs() {
    luaopen_timer_native(m_state);
}
